/* This file is part of REWise.
 *
 * REWise is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * REWise is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

#include <errno.h>

#include "inflate.h"
#include "errors.h"
#include "print.h"


static HuffmanNode * FixedLiteralTree  = NULL;
static HuffmanNode * FixedDistanceTree = NULL;

// Constant defined in the PKZIP APPNOTE (5.5.3 Expanding Huffman Codes)
// Also here https://www.rfc-editor.org/rfc/rfc1951#page-13
static unsigned char CODE_LENGTH_ORDER[19] = {
  0x10, 0x11, 0x12, 0x00, 0x08, 0x07, 0x09, 0x06, 0x0A, 0x05, 0x0B, 0x04,
  0x0C, 0x03, 0x0D, 0x02, 0x0E, 0x01, 0x0F
};

// DEFLATE static dictionary (Bit reduction)
static int LENGTH_CODE_VALUE_OFFSET[286] = {
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000000, 0x00000000, 0x00000000,
  0x00000000, 0x00000003, 0x00000004, 0x00000005,
  0x00000006, 0x00000007, 0x00000008, 0x00000009,
  0x0000000A, 0x0000000B, 0x0000000D, 0x0000000F,
  0x00000011, 0x00000013, 0x00000017, 0x0000001B,
  0x0000001F, 0x00000023, 0x0000002B, 0x00000033,
  0x0000003B, 0x00000043, 0x00000053, 0x00000063,
  0x00000073, 0x00000083, 0x000000A3, 0x000000C3,
  0x000000E3, 0x00000102

};

static unsigned char LENGTH_CODE_VALUE_EXTRA_BITS[286] = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02,
  0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04,
  0x04, 0x05, 0x05, 0x05, 0x05, 0x00
};

static int DISTANCE_CODE_VALUE_OFFSET[30] = {
  0x00000001, 0x00000002, 0x00000003, 0x00000004,
  0x00000005, 0x00000007, 0x00000009, 0x0000000D,
  0x00000011, 0x00000019, 0x00000021, 0x00000031,
  0x00000041, 0x00000061, 0x00000081, 0x000000C1,
  0x00000101, 0x00000181, 0x00000201, 0x00000301,
  0x00000401, 0x00000601, 0x00000801, 0x00000C01,
  0x00001001, 0x00001801, 0x00002001, 0x00003001,
  0x00004001, 0x00006001
};

static unsigned char DISTANCE_CODE_VALUE_EXTRA_BITS[30] = {
  0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x02,
  0x03, 0x03, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06,
  0x07, 0x07, 0x08, 0x08, 0x09, 0x09, 0x0A, 0x0A,
  0x0B, 0x0B, 0x0C, 0x0C, 0x0D, 0x0D
};


/* newHuffmanNode() - Create new Huffman node. This allocates memory and inits
 *                    the new node.
 *
 * @returns: Pointer to the new Huffman node or NULL on error. */
HuffmanNode * newHuffmanNode(void) {
  HuffmanNode * newNode = NULL;
  newNode = malloc(sizeof(HuffmanNode));

  if (newNode == NULL) {
    printError("newHuffmanNode errno: %s\n", strerror(errno));
    return NULL;
  }

  newNode->value = 0xffff;
  newNode->childeren[HUFFMAN_LEFT]  = NULL;
  newNode->childeren[HUFFMAN_RIGHT] = NULL;
  newNode->leafes[HUFFMAN_LEFT]  = false;
  newNode->leafes[HUFFMAN_RIGHT] = false;

  return newNode;
}


/* HuffmanAddCode() - Adds new code to a Huffman tree.
 *
 * @param node      : Node to add the code to.
 * @param codeLength: The traverse count to the node we want to set the value
 *                    to (count from given 'node').
 * @param codeValue : The value to set.
 *
 * @returns: 'true' on success, 'false' on error. */
bool HuffmanAddCode(HuffmanNode * node, unsigned char codeLength, int codeValue) {
  bool result = false;

  if (codeLength == 0) {
    printError("HuffmanAddCode codeLength == 0\n");
    return result;
  }

  // Left child of current node is not a end node
  if (node->leafes[HUFFMAN_LEFT] == false) {
    // Add new left node
    if (node->childeren[HUFFMAN_LEFT] == NULL) {
      HuffmanNode * newNode = newHuffmanNode();
      if (newNode == NULL) {
        return false;
      }
      node->childeren[HUFFMAN_LEFT] = newNode;
    }

    // Left (new) child is a end node
    if (codeLength == 1) {
      node->leafes[HUFFMAN_LEFT] = true;
      node->childeren[HUFFMAN_LEFT]->value = codeValue;
      result = true;
    }
    else {
      result = HuffmanAddCode(node->childeren[HUFFMAN_LEFT], codeLength - 1, codeValue);
    }
  }

  if (result == false) {
    // Right child of current node is not a end node
    if (node->leafes[HUFFMAN_RIGHT] == false) {
      // Set left child as a end node ???
      node->leafes[HUFFMAN_LEFT] = true;

      // Add new right node
      if (node->childeren[HUFFMAN_RIGHT] == NULL) {
        HuffmanNode * newNode = newHuffmanNode();
        if (newNode == NULL) {
          return false;
        }
        node->childeren[HUFFMAN_RIGHT] = newNode;
      }

      // The new right child is a end node
      if (codeLength == 1) {
        node->leafes[HUFFMAN_RIGHT] = true;
        node->childeren[HUFFMAN_RIGHT]->value = codeValue;
        result = true;
      }
      else {
        result = HuffmanAddCode(node->childeren[HUFFMAN_RIGHT], codeLength - 1, codeValue);
      }
    }
    else {
      node->leafes[HUFFMAN_RIGHT] = true;
    }
  }

  return result;
}


/* huffmanFreeTree() - Free a Huffman tree.
 *
 * @param node: Root node of the Huffman tree. */
void huffmanFreeTree(HuffmanNode * node) {
  if (node->childeren[HUFFMAN_LEFT] != NULL) {
    huffmanFreeTree(node->childeren[HUFFMAN_LEFT]);
    node->childeren[HUFFMAN_LEFT] = NULL;
  }

  if (node->childeren[HUFFMAN_RIGHT] != NULL) {
    huffmanFreeTree(node->childeren[HUFFMAN_RIGHT]);
    node->childeren[HUFFMAN_RIGHT] = NULL;
  }

  free(node);
}


/* huffmanInitFixedTrees() - Build fixed DEFLATE Huffman tree.
 * NOTE: huffmanFreeFixedTrees() should be called when this has returned true!
 **/
bool huffmanInitFixedTrees(void) {
  HuffmanNode * literalTree = newHuffmanNode();
  if (literalTree == NULL) {
    return false;
  }

  // 256 - 279
  for (uint16_t value=256; value < 280; value++) {
    if (HuffmanAddCode(literalTree, 7, value) == false) {
      huffmanFreeTree(literalTree);
      return false;
    }
  }

  // 0 - 143
  for (uint16_t value=0; value < 144; value++) {
    if (HuffmanAddCode(literalTree, 8, value) == false) {
      huffmanFreeTree(literalTree);
      return false;
    }
  }

  // 280 - 287
  for (uint16_t value=280; value < 288; value++) {
    if (HuffmanAddCode(literalTree, 8, value) == false) {
      huffmanFreeTree(literalTree);
      return false;
    }
  }

  // 144 - 255
  for (uint16_t value=144; value < 256; value++) {
    if (HuffmanAddCode(literalTree, 9, value) == false) {
      huffmanFreeTree(literalTree);
      return false;
    }
  }

  HuffmanNode * distanceTree = newHuffmanNode();
  if (distanceTree == NULL) {
    huffmanFreeTree(literalTree);
    return false;
  }

  // 0 - 31
  for (unsigned char value=0; value < 32; value++) {
    if (HuffmanAddCode(distanceTree, 5, value) == false) {
      huffmanFreeTree(literalTree);
      huffmanFreeTree(distanceTree);
      return false;
    }
  }

  FixedLiteralTree  = literalTree;
  FixedDistanceTree = distanceTree;

  return true;
}


void huffmanFreeFixedTrees(void) {
  huffmanFreeTree(FixedLiteralTree);
  huffmanFreeTree(FixedDistanceTree);
  FixedLiteralTree  = NULL;
  FixedDistanceTree = NULL;
}


// forward deceleration
int inflateReadBits(InflateObject * inflateObj, uint8_t bitCount);


// return value larger then 0x11e is error
/* huffmanReadValue() - Keep reading 1 bit until we arrive at a leaf of the
 *                      given 'tree' and return that value.
 *
 * @param inflateObj: Inflate descriptor.
 * @param tree      : The Huffman tree for resolving the value.
 *
 * @returns: The real value, or 0xffffffff on error. */
int huffmanReadValue(InflateObject * inflateObj, HuffmanNode * tree) {
  HuffmanNode * node = tree;
  // loop until a leaf node has reached
  while (node->value == 0xffff) {
    int direction = inflateReadBits(inflateObj, 1);
    if (direction > 1) {
      printError("huffmanReadValue inflateReadBits failed\n");
      return 0xffffffff; // error
    }
    if (node->childeren[direction] == NULL) {
      printError("huffmanReadValue the tree is incomplete or invalid bit path "
                 "requested.\n");
      return 0xffffffff; // error
    }
    node = node->childeren[direction];
  }
  return node->value;
}


/* inflateInit() - This should be called on every new InflateObject to init it.
 *
 * @param inflateObj: Inflate descriptor.
 * @param inputFile : Valid and open FILE to the input PE file (installer exe),
 *                    this may NOT be NULL!
 **/
void inflateInit(InflateObject * inflateObj, FILE * inputFile) {
  memset(inflateObj->window   , 0x00, sizeof(unsigned char) * WINDOW_SIZE);
  memset(inflateObj->chunkBuff, 0x00, sizeof(unsigned char) * CHUNK_SIZE);
  inflateObj->bitOffset         = 8; // trigger read new byte
  inflateObj->windowPosition    = 0;
  inflateObj->chunkBuffPosition = CHUNK_SIZE; // set to max, to trigger new read
  inflateObj->chunkBuffSize     = CHUNK_SIZE;
  inflateObj->inputFile         = inputFile;
  inflateObj->outputFile        = NULL;
  inflateObj->outputSize        = 0;
  inflateObj->wantedOutputSize  = -1;
  inflateObj->crc32             = CRC32_NEW;

  long inputFileOffset = ftell(inputFile); // backup input positions
  fseek(inputFile, 0L, SEEK_END); // seek to end
  inflateObj->inputFileSize = ftell(inputFile); // store file size
  fseek(inputFile, inputFileOffset, SEEK_SET); // restore input position
}


/* inflateNew() - Prepare the 'InflateObject' for a new file to extract. This
 *                should be called before trying to extract a file.
 *
 * @param inflateObj: Inflate descriptor.
 * @param outputFile: Should be a valid and open FILE to actually inflate to a
 *                    output file. This may be NULL to not write the inflated
 *                    data to a file.
 * @param wantedOutputSize:
 *                    The wanted inflated size defined by the WiseScript.bin
 *                    When this is set to -1 it will NOT be checked. */
void inflateNew(InflateObject * inflateObj, FILE * outputFile,
                long wantedOutputSize)
{
  inflateObj->bitOffset         = 8; // trigger read new byte
  inflateObj->windowPosition    = 0;
  inflateObj->chunkBuffPosition = CHUNK_SIZE; // set to max, to trigger new read
  inflateObj->chunkBuffSize     = CHUNK_SIZE;
  inflateObj->outputFile        = outputFile;
  inflateObj->outputSize        = 0;
  inflateObj->wantedOutputSize  = wantedOutputSize;
  inflateObj->crc32             = CRC32_NEW;
}


/* inflateRead() - Read a byte from the inflate chunk buffer.
 *
 * @returns: A value greater then 0xff on error, else it will return the value.
 **/
uint16_t inflateRead(InflateObject * inflateObj) {
  // Read new chunk
  if (inflateObj->chunkBuffPosition >= inflateObj->chunkBuffSize) {
    // End Of File
    if (ftell(inflateObj->inputFile) == inflateObj->inputFileSize) {
      printError("inflateRead EOF.\n");
      return 0xffff;
    }

    // Last chunk of the inputFile is smaller then 16k, adjust the
    // inputBuffSize
    if (inflateObj->chunkBuffPosition > (inflateObj->inputFileSize - ftell(inflateObj->inputFile))) {
      inflateObj->chunkBuffSize = inflateObj->inputFileSize - ftell(inflateObj->inputFile);
    }

    // Read next chunk
    REWError status = readBytesInto(inflateObj->inputFile, inflateObj->chunkBuff, inflateObj->chunkBuffSize);
    if (status != REWERR_OK) {
      // failed to read next chunk
      printError("inflateRead failed to read next chunk.\n");
      return 0xffff;
    }
    inflateObj->chunkBuffPosition = 0;
  }

  uint16_t result = (uint16_t)inflateObj->chunkBuff[inflateObj->chunkBuffPosition];
  inflateObj->chunkBuffPosition++;
  return result;
}


/* inflateReadBits() - Reads 'bitCount' of bits from the chunk buffer.
 *
 * @param inflateObj: Inflate descriptor.
 * @param bitCount  : The amount of bits to read. (TODO define maximum bitCount)
 *
 * @returns: The value on success, 0xffffffff on error. */
int inflateReadBits(InflateObject * inflateObj, uint8_t bitCount) {
  int result       = 0;
  int resultOffset = 0;

  while (bitCount > 0) {
    // Read new byte into buffer
    if (inflateObj->bitOffset == 8) {
      uint16_t readResult   = inflateRead(inflateObj);
      if (readResult > 0xff) {
        // inflateRead error
        return 0xffffffff;
      }
      inflateObj->bitBuff   = (unsigned char)readResult;
      inflateObj->bitOffset = 0;
    }

    int mask = 1 << inflateObj->bitOffset;
    if ((inflateObj->bitBuff & mask)) {
      result |= 1 << resultOffset;
    }

    inflateObj->bitOffset++;
    bitCount--;
    resultOffset++;
  }

  return result;
}


/* huffmanReadDynamicTrees() - Read and build dynamic Huffman trees.
 *
 * @param inflateObj: Inflate descriptor.
 * @param hlit        : Literal/Length codes
 * @param hdist       : Distance codes
 * @param hclen       : Code Length codes
 * @param literalTree : Literal tree destination on success, this should be
 *                      freed with huffmanFreeTree() on success.
 * @param distanceTree: Distance tree destination on success, this should be
 *                      freed with huffmanFreeTree() on success.
 *
 * @returns: 'true' on success, 'false' on error. */
bool huffmanReadDynamicTrees(InflateObject * inflateObj, int hlit, int hdist,
                             int hclen, HuffmanNode ** literalTree,
                             HuffmanNode ** distanceTree) {
  bool result;
  int repeatAmount;
  unsigned char repeatValue;
  unsigned char maxCodeLength, maxCodeLength2;
  unsigned char codeLengths[19];
  unsigned char codeLengthAlphabet[318];
  unsigned char codeLength;
  int codeValue;
  int readBitsResult;

  memset(codeLengths, 0x00, sizeof(unsigned char) * 19);
  memset(codeLengthAlphabet, 0x00, sizeof(unsigned char) * 318);
  maxCodeLength = 0;

  // Read codeLengths
  for (uint16_t i=0; i < hclen; i++) {
    readBitsResult = inflateReadBits(inflateObj, 3);
    if (readBitsResult == 0xffffffff) {
      // inflateReadBits error
      printError("Failed to read dynamic trees.\n");
      return false;
    }
    codeLength = (unsigned char)readBitsResult;

    if (codeLength > maxCodeLength) {
      maxCodeLength = codeLength;
    }

    codeLengths[CODE_LENGTH_ORDER[i]] = codeLength;
  }

  // Build codeLengthTree
  HuffmanNode * codeLengthTree = newHuffmanNode();
  if (codeLengthTree == NULL) {
    printError("huffmanReadDynamicTrees failed to build dynamic code length "
               "tree.\n");
    printError("huffmanReadDynamicTrees errno: %s\n", strerror(errno));
    return false;
  }
  result = false;
  // 1 - 16
  for (codeLength = 0x01; codeLength < 0x10; codeLength++) {
    // 0 - 18
    for (codeValue = 0x00; codeValue < 0x13; codeValue++) {
      if (codeLength == codeLengths[codeValue]) {
        result = HuffmanAddCode(codeLengthTree, codeLength, codeValue);
      }
    }
  }

  if (result == false) {
    huffmanFreeTree(codeLengthTree);
    printError("huffmanReadDynamicTrees failed to build dynamic code length "
               "tree.\n");
    return false;
  }

  if (maxCodeLength == 0) {
    maxCodeLength++;
  }

  result = !HuffmanAddCode(codeLengthTree, maxCodeLength, 0);
  if (result == false) {
    huffmanFreeTree(codeLengthTree);
    printError("huffmanReadDynamicTrees ailed to build dynamic code length "
               "tree. It has nodes without end-node.\n");
    return false;
  }

  // Build alphabet
  repeatAmount   = 0;
  repeatValue    = 0;
  maxCodeLength  = 0;
  maxCodeLength2 = 0;
  for (codeValue=0; codeValue < (int)(hlit + hdist); codeValue++) {
    if (repeatAmount == 0) {
      // read and decode codeLength
      codeLength = (unsigned char)huffmanReadValue(inflateObj, codeLengthTree);

      if (codeLength < 0x10) {
        codeLengthAlphabet[codeValue] = codeLength;

        // Update maxBits/maxBits2
        if (codeValue < hlit) {
          if (codeLength > maxCodeLength) {
            maxCodeLength = codeLength;
          }
        }
        else {
          if (codeLength > maxCodeLength2) {
            maxCodeLength2 = codeLength;
          }
        }
      }
      else if (codeLength == 0x10) {
        readBitsResult = inflateReadBits(inflateObj, 2);
        if (readBitsResult == 0xffffffff) {
          printError("Failed to read dynamic trees.\n");
          return false;
        }
        repeatAmount                  = 0x02 + readBitsResult;
        repeatValue                   = codeLengthAlphabet[codeValue - 0x01];
        codeLengthAlphabet[codeValue] = repeatValue;
      }
      else if (codeLength == 0x11) {
        readBitsResult = inflateReadBits(inflateObj, 3);
        if (readBitsResult == 0xffffffff) {
          printError("Failed to read dynamic trees.\n");
          return false;
        }
        repeatAmount                  = 0x02 + readBitsResult;
        repeatValue                   = 0;
        codeLengthAlphabet[codeValue] = repeatValue;
      }
      else if (codeLength == 0x12) {
        readBitsResult = inflateReadBits(inflateObj, 7);
        if (readBitsResult == 0xffffffff) {
          printError("Failed to read dynamic trees.\n");
          return false;
        }
        repeatAmount                  = 0x0A + readBitsResult;
        repeatValue                   = 0;
        codeLengthAlphabet[codeValue] = repeatValue;
      }
    }
    else {
      codeLengthAlphabet[codeValue] = repeatValue;
      repeatAmount--;
    }
  }

  // free the codeLengthTree, we don't need it anymore.
  huffmanFreeTree(codeLengthTree);

  // build literal tree
  HuffmanNode * litTree = newHuffmanNode();
  if (litTree == NULL) {
    printError("huffmanReadDynamicTrees failed to allocate literalTree.\n");
    printError("huffmanReadDynamicTrees errno: %s\n", strerror(errno));
    return false;
  }
  for (codeLength=0x01; codeLength < 0x10; codeLength++) {
    for (codeValue=0; codeValue < hlit; codeValue++) {
      if (codeLength == codeLengthAlphabet[codeValue]) {
        result = HuffmanAddCode(litTree, codeLength, codeValue);
      }
    }
  }

  if (result == true) {
    if (maxCodeLength == 0) {
      maxCodeLength++;
    }
    result = !HuffmanAddCode(litTree, maxCodeLength, 0);
    if (result == false) {
      huffmanFreeTree(litTree);
      printError("huffmanReadDynamicTrees failed to build dynamic literal "
                 "tree (1). It has a open branch without leaf?\n");
      return false;
    }
  }
  else {
    huffmanFreeTree(litTree);
    printError("huffmanReadDynamicTrees failed to build dynamic literal tree "
               "(2).\n");
    return false;
  }

  // build distance tree
  HuffmanNode * distTree = newHuffmanNode();
  if (distTree == NULL) {
    printError("huffmanReadDynamicTrees failed to allocate distanceTree.\n");
    printError("huffmanReadDynamicTrees errno: %s\n", strerror(errno));
    return false;
  }
  for (codeLength=0x01; codeLength < 0x10; codeLength++) {
    for (codeValue=0; codeValue < hdist; codeValue++) {
      if (codeLength == codeLengthAlphabet[codeValue + hlit]) {
        result = HuffmanAddCode(distTree, codeLength, codeValue);
      }
    }
  }

  if (result == true) {
    if (maxCodeLength2 == 0) {
      maxCodeLength2++;
    }
    result = !HuffmanAddCode(distTree, maxCodeLength2, 0);
    if (result == false) {
      huffmanFreeTree(litTree);
      huffmanFreeTree(distTree);
      printError("huffmanReadDynamicTrees failed to build dynamic distance "
                 "tree (1).\n");
      return false;
    }
  }
  else {
    huffmanFreeTree(litTree);
    huffmanFreeTree(distTree);
    printError("huffmanReadDynamicTrees failed to build dynamic distance tree "
               "(2).\n");
    return false;
  }

  *literalTree  = litTree;
  *distanceTree = distTree;

  return true;
}


#ifdef REWISE_DEV_DEBUG
void inflateInitStaticTables(void) {
  int lengthOffset = 3;
  unsigned char lengthExtraBits = 0;
  int distanceOffset = 1;
  unsigned char distanceExtraBits = 0;

  LENGTH_CODE_VALUE_OFFSET[285]     = 258; // 258 = 0x102
  LENGTH_CODE_VALUE_EXTRA_BITS[285] = 0;

  for (unsigned char pos=0; pos < 30; pos++) {
    // increment number of extra bits for length code table every 4th value
    if (pos >= 0x08 && !(pos & 0x03)) {
      lengthExtraBits++;
    }

    // increment number of extra bits for distance code table every 2nd value
    if (pos >= 0x04 && !(pos & 0x01)) {
      distanceExtraBits++;
    }

    // for pos<0x1c put value entry into length code table
    if (pos < 0x1c) {
      LENGTH_CODE_VALUE_OFFSET[pos + 0x101]     = lengthOffset;
      LENGTH_CODE_VALUE_EXTRA_BITS[pos + 0x101] = lengthExtraBits;
    }

    // put value entry into distance code table
    DISTANCE_CODE_VALUE_OFFSET[pos]     = distanceOffset;
    DISTANCE_CODE_VALUE_EXTRA_BITS[pos] = distanceExtraBits;

    // increment length and distance code values
    lengthOffset   += (1 << lengthExtraBits);
    distanceOffset += (1 << distanceExtraBits);
  }
}


void inflatePrintStaticTables(void) {
  uint8_t colCount;

  colCount = 4;
  printf("LENGTH_CODE_VALUE_OFFSET\n");
  for (uint32_t i=0; i<286; i++) {
    printf("0x%08X", LENGTH_CODE_VALUE_OFFSET[i]);
    uint8_t colMod = (i+1) % colCount;
    if (i == 285) { printf("\n"); }
    else if (colMod) { printf(", "); }
    else { printf(",\n"); }
  }

  colCount = 8;
  printf("\n");
  printf("LENGTH_CODE_VALUE_EXTRA_BITS\n");
  for (uint32_t i=0; i<286; i++) {
    printf("0x%02X", LENGTH_CODE_VALUE_EXTRA_BITS[i]);
    if (i == 285) { printf("\n"); }
    else if ((i+1) % colCount) { printf(", "); }
    else { printf(",\n"); }
  }

  colCount = 4;
  printf("\n");
  printf("DISTANCE_CODE_VALUE_OFFSET\n");
  for (uint32_t i=0; i<30; i++) {
    printf("0x%08X", DISTANCE_CODE_VALUE_OFFSET[i]);
    if (i == 29) { printf("\n"); }
    else if ((i+1) % colCount) { printf(", "); }
    else { printf(",\n"); }
  }

  colCount = 8;
  printf("\n");
  printf("DISTANCE_CODE_VALUE_EXTRA_BITS\n");
  for (uint32_t i=0; i<30; i++) {
    printf("0x%02X", DISTANCE_CODE_VALUE_EXTRA_BITS[i]);
    if (i == 29) { printf("\n"); }
    else if ((i+1) % colCount) { printf(", "); }
    else { printf(",\n"); }
  }
}
#endif


/* inflateWriteOutput() - Writes the content of the sliding window to the
 *                        'InflateObject->outputFile' when it is not 'NULL'.
 *
 *                        It will also keep track of the total number of bytes
 *                        written to this file and update the CRC32.
 *
 *                        This does not reset the sliding window position, the
 *                        caller is responsible for that when this returns
 *                        'true'.
 *
 * @param inflateObj: Inflate descriptor.
 * @param size      : Amount of bytes in the sliding window to write.
 *
 * @returns: 'true' on success, 'false' on error. */
bool inflateWriteOutput(InflateObject * inflateObj, uint32_t size) {
  long newOutputSize = inflateObj->outputSize + (long)size;

  // Check that the outputSize is not larger then the size defined in the
  // WiseScript.bin
  if (inflateObj->wantedOutputSize >= 0) {
    if (newOutputSize > inflateObj->wantedOutputSize) {
      printError("Inflated data is larger then the filesize defined in the "
                 "WiseScript.bin\n");
      return false;
    }
  }

  // Write to output file
  if (inflateObj->outputFile != NULL) {
    size_t noWritten = fwrite(inflateObj->window, sizeof(unsigned char),
                              (size_t)size, inflateObj->outputFile);
    if (noWritten != (size_t)size) {
      // Failed to write to file, out of disk space or something got corrupted.
      printError("Failed to write to file, only wrote %ld of %ld bytes. "
                 "Out of disk space?\n", noWritten, size);
      return false;
    }
  }

  inflateObj->outputSize = newOutputSize;

  // Update CRC32
  inflateObj->crc32 = crc32Update(inflateObj->crc32, inflateObj->window, size);
  return true;
}


/* inflateOutputByte() - Appends the given 'byte' to the sliding window, when
 *                       the sliding window is full it will output the sliding
 *                       window to 'inflateObj->outputFile' (when it isn't
 *                       'NULL') and resets the sliding window.
 *
 * @param inflateObj: Inflate descriptor.
 * @param byte      : The 'byte' to output.
 *
 * @returns: 'true' on success, 'false' on error. */
bool inflateOutputByte(InflateObject * inflateObj, unsigned char byte) {
  inflateObj->window[inflateObj->windowPosition] = byte;
  inflateObj->windowPosition++;

  if (inflateObj->windowPosition == WINDOW_SIZE) {
    if (inflateWriteOutput(inflateObj, WINDOW_SIZE) == false) {
      return false;
    }
    inflateObj->windowPosition = 0;
  }

  return true;
}


/* inflateCopyBytes() - Copy 'codeLength' amount of bytes (with 'codeDistance'
 *                      offset) from the sliding window to the end of the
 *                      sliding window.
 * @param inflateObj  : Inflate descriptor.
 * @param codeDistance: Offset from the end of the current sliding window.
 * @param codeLength  : Amount of bytes to copy and append to the end of the
 *                      current sliding window.
 *
 * @returns: 'true' on success, 'false' on error. */
bool inflateCopyBytes(InflateObject * inflateObj, int codeDistance,
                      int codeLength)
{
  while (codeLength > 0) {
    unsigned char byte = inflateObj->window[
      (inflateObj->windowPosition + WINDOW_SIZE - codeDistance) & 0x7fff];
    if (inflateOutputByte(inflateObj, byte) == false) {
      return false;
    }
    codeLength--;
  }
  return true;
}


/* inflateNext() - Inflate next file.
 *
 * @returns: 'true' on success, 'false' on error. */
bool inflateNext(InflateObject * inflateObj) {
  unsigned char lastBlock;
  unsigned char blockType;
  int hclen;
  int hdist;
  int hlit;

  // TODO just for DEBUG
  inflateObj->deflatedStart = ftell(inflateObj->inputFile);
  printDebug("deflatedStart: %08lX\n", inflateObj->deflatedStart);

  lastBlock = 0;
  while (lastBlock == 0) {
    // read lastBlock
    int readBitsResult = inflateReadBits(inflateObj, 1);
    if (readBitsResult == 0xffffffff) {
      return false;
    }
    lastBlock = (unsigned char)readBitsResult;

    // read blockType
    readBitsResult = inflateReadBits(inflateObj, 2);
    if (readBitsResult == 0xffffffff) {
      return false;
    }
    blockType = (unsigned char)readBitsResult;

    if (blockType == BTYPE_UNCOMPRESSED) {
      // Make sure we start on a fresh byte (aligned)
      inflateObj->bitOffset = 8;

      hclen = inflateRead(inflateObj) + (inflateRead(inflateObj) << 8);
      hdist = inflateRead(inflateObj) + (inflateRead(inflateObj) << 8);

      if ((hclen ^ hdist) != 0xffff) {
        printError("Code-length or code-distance invalid! 0x%04X hclen: 0x%02X hdist: 0x%02X\n", (hclen ^ hdist), hclen, hdist);
        return false;
      }

      while (hclen > 0) {
        // read hlit
        uint16_t readResultHlit = inflateRead(inflateObj);
        if (readResultHlit > 255) {
          return false;
        }
        //hlit = (unsigned char)readResult;
        if (inflateOutputByte(inflateObj, (unsigned char)readResultHlit) == false) {
          return false;
        }
        hclen--;
      }
    }
    else
    if (blockType == BTYPE_FIXED) {
      hlit = 0;
      while (hlit != 0x100) {
        hlit = huffmanReadValue(inflateObj, FixedLiteralTree);

        if (hlit < 0x100) {
          if (inflateOutputByte(inflateObj, (unsigned char)hlit) == false) {
            return false;
          }
        }
        else
        if (hlit == 0x100) {
          break;
        }
        else
        if (hlit < 0x11e) {
          // Read code length extra bits
          readBitsResult = inflateReadBits(inflateObj,
                                           LENGTH_CODE_VALUE_EXTRA_BITS[hlit]);
          if (readBitsResult == 0xffffffff) {
            // inflateReadBits error
            printError("Failed to read fixed length extra bits.\n");
            return false;
          }
          int codeLength   = LENGTH_CODE_VALUE_OFFSET[hlit] + readBitsResult;
          int codeDistance = huffmanReadValue(inflateObj, FixedDistanceTree);

          if (codeDistance > 0x1d) {
            printError("Unexpected code distance 0x%08X\n", codeDistance);
            return false;
          }

          // Read code distance extra bits
          readBitsResult = inflateReadBits(inflateObj,
                                           DISTANCE_CODE_VALUE_EXTRA_BITS[codeDistance]);
          if (readBitsResult == 0xffffffff) {
            printError("Failed to read fixed distance extra bits.\n");
            return false;
          }
          codeDistance = DISTANCE_CODE_VALUE_OFFSET[codeDistance] + readBitsResult;
          if (inflateCopyBytes(inflateObj, codeDistance, codeLength) == false) {
            return false;
          }
        }
        else {
          printError("Unexpected literal 0x%08X\n", hlit);
          return false;
        }
      }
    }
    else
    if (blockType == BTYPE_DYNAMIC) {
      // Read hlit
      readBitsResult = inflateReadBits(inflateObj, 5);
      if (readBitsResult == 0xffffffff) {
        printError("Failed to read dynamic hlit bits.\n");
        return false;
      }
      hlit = readBitsResult + 0x101;

      // Read hdist
      readBitsResult = inflateReadBits(inflateObj, 5);
      if (readBitsResult == 0xffffffff) {
        printError("Failed to read dynamic hdist bits.\n");
        return false;
      }
      hdist = readBitsResult + 0x01;

      // Read hclen
      readBitsResult = inflateReadBits(inflateObj, 4);
      if (readBitsResult == 0xffffffff) {
        printError("Failed to read dynamic hclen bits.\n");
        return false;
      }
      hclen = readBitsResult + 0x04;

      HuffmanNode * literalTree  = NULL;
      HuffmanNode * distanceTree = NULL;

      if (huffmanReadDynamicTrees(inflateObj, hlit, hdist, hclen, &literalTree,
                                  &distanceTree) == false) {
        printError("Failed to build dynamic trees\n");
        return false;
      }

      while (hlit != 0x100) {
        hlit = huffmanReadValue(inflateObj, literalTree);

        if (hlit < 0x100) {
          if (inflateOutputByte(inflateObj, (unsigned char)hlit) == false) {
            huffmanFreeTree(literalTree);
            huffmanFreeTree(distanceTree);
            return false;
          }
        }
        else
        if (hlit == 0x100) {
          break;
        }
        else
        if (hlit < 0x11e) {
          // Read code value extra bits
          readBitsResult = inflateReadBits(inflateObj, LENGTH_CODE_VALUE_EXTRA_BITS[hlit]);
          if (readBitsResult == 0xffffffff) {
            huffmanFreeTree(literalTree);
            huffmanFreeTree(distanceTree);
            printError("Failed to read dynamic code value extra bits.\n");
            return false;
          }
          int codeLength   = LENGTH_CODE_VALUE_OFFSET[hlit] + readBitsResult;
          int codeDistance = huffmanReadValue(inflateObj, distanceTree);

          // Read distance value extra bits
          readBitsResult = inflateReadBits(inflateObj, DISTANCE_CODE_VALUE_EXTRA_BITS[codeDistance]);
          if (readBitsResult == 0xffffffff) {
            huffmanFreeTree(literalTree);
            huffmanFreeTree(distanceTree);
            printError("Failed to read dynamic distance value extra bits.\n");
            return false;
          }

          codeDistance = DISTANCE_CODE_VALUE_OFFSET[codeDistance] + readBitsResult;
          if (inflateCopyBytes(inflateObj, codeDistance, codeLength) == false) {
            huffmanFreeTree(literalTree);
            huffmanFreeTree(distanceTree);
            return false;
          }
        }
        else {
          huffmanFreeTree(literalTree);
          huffmanFreeTree(distanceTree);
          printError("Unexpected literal 0x%08X\n", hlit);
          return false;
        }
      }

      // free dynamic trees
      huffmanFreeTree(literalTree);
      huffmanFreeTree(distanceTree);
    }
    else {
      printError("Unknown block type! '%X'\n", blockType);
      return false;
    }
  }

  // Write leftover (smaller then WINDOW_SIZE)
  if (inflateObj->windowPosition > 0) {
    if (inflateWriteOutput(inflateObj, inflateObj->windowPosition) == false) {
      return false;
    }
  }

  return true;
}


/* inflateExtractNextFile() - Inflate helper that opens/creates the output file
 *                            when 'outputFilePath' is not 'NULL'. This also
 *                            reads and checks the CRC32. The input file offset
 *                            should be EOF or the beginning of the next
 *                            deflated file data after this returns 'true'.
 *
 * @param inflateObj    : Inflate descriptor.
 * @param outputFilePath: File path to extract the data to, this may be 'NULL'
 *                        to not extract to a file (for just verify crc32 or to
 *                        skip a file).
 * @param wantedOutputSize:
 *                        The wanted inflated size defined by the WiseScript.bin
 *                        When this is set to -1 it will NOT be checked.
 *
 * @returns: 'true' on success, 'false' on error. */
bool inflateExtractNextFile(InflateObject * inflateObj,
                            const char * outputFilePath,
                            long wantedOutputSize) {
  FILE * outputFp;
  bool result;
  uint32_t newCrc;

  if (outputFilePath != NULL) {
    outputFp = fopen(outputFilePath, "wb");
    if (outputFp == NULL) {
      printError("Failed to open output file '%s'\n", outputFilePath);
      return false;
    }
  }
  else {
    outputFp = NULL;
  }

  // start inflation process
  inflateNew(inflateObj, outputFp, wantedOutputSize);
  result = inflateNext(inflateObj);

  // close output file when there is any
  if (outputFp != NULL) {
    fclose(outputFp);
  }

  // inflation failed
  if (result == false) {
    return false;
  }

  // inflation success
  inflateObj->crc32 = crc32Finalize(inflateObj->crc32);

  // Seek to the end of the deflate data since we probably overshot due to the
  // chunk buffer.
  if (fseek(inflateObj->inputFile, ftell(inflateObj->inputFile) -
                               inflateObj->chunkBuffSize +
                               inflateObj->chunkBuffPosition, SEEK_SET) != 0)
  {
    printError("Failed to seek back to deflate end.\n");
    return false;
  }

  // Check that the filesize defined in WiseScript.bin matches with the written
  // byte count. (Do not check when wantedOutputSize is smaller then 0)
  if (inflateObj->wantedOutputSize >= 0) {
    if (inflateObj->outputSize != inflateObj->wantedOutputSize) {
      printError("Inflate output size does not match the filesize we got from "
                 "WiseScript.bin.\n");
      return false;
    }
  }

  // Read and check CRC32
  if (readUInt32(inflateObj->inputFile, &newCrc) != REWERR_OK) {
    printError("Failed to read CRC32\n");
    return false;
  }

  if (inflateObj->crc32 != newCrc) {
    // deflated data should be 8 byte aligned?
    // NOTE: largest offset I have seen is 1. Probably because of the bits read.
    uint8_t attempt;
    for (attempt=0; attempt<8; attempt++) {
      if (fseek(inflateObj->inputFile, -3l, SEEK_CUR) != 0) {
        printError("Failed to seek to next crc attempt.\n");
        return false;
      }
      if (readUInt32(inflateObj->inputFile, &newCrc) != REWERR_OK) {
        printError("Failed to read CRC32 attempt\n");
        return false;
      }
      if (inflateObj->crc32 == newCrc) {
        break;
      }
    }

    if (inflateObj->crc32 != newCrc) {
      printError("CRC32 mismatch\n");
      return false;
    }

    printDebug("crc32 attempt %d\n", attempt);
  }

  return true;
}
